package manager

import (
	"container/list"
	gojson "encoding/json"
	"fmt"
	"github.com/tianjigames/fairy/constants"
	"github.com/tianjigames/fairy/model"
	protos2 "github.com/tianjigames/fairy/protos"
	"github.com/tianjigames/fairy/util"
	"github.com/topfreegames/pitaya"
	"github.com/topfreegames/pitaya/component"
	constants2 "github.com/topfreegames/pitaya/constants"
	"github.com/topfreegames/pitaya/logger"
	"github.com/topfreegames/pitaya/queue"
	"reflect"
	"time"
)

var (
	typeOfLoginInfo = reflect.TypeOf(&model.LoginInfo{})
)

type (
	LoginThread struct {
		component.LogicThread
		waitLoginQueueForGMs *queue.BaseQueue
		waitLoginQueue *queue.BaseQueue
		waitLoginMap map[string] *list.Element
		workerPool *RemoteLoginThreadPool
	}

	RemoteLoginThreadPool struct {
		loginChan chan *model.LoginInfo
		workers []component.LogicThread
	}
)


func (p *LoginThread) Init()  {
	p.LogicThread.Init()
	p.workerPool.Init()
}


func (p *LoginThread) Shutdown()  {
	p.LogicThread.Shutdown()
	p.workerPool.Shutdown()
}

func (p *RemoteLoginThreadPool) Init()  {
	workerSize := 4
	sleep := 100
	run := func() {
		select {
		case info :=<-p.loginChan:
			LoginManager.GetLoginLogic(info.ChannelId).DoLogin(info.Ctx,info)
		default:
			time.Sleep(time.Duration(sleep)*time.Millisecond)
		}
	}

	for i:=0;i<workerSize;i++ {
		worker := component.LogicThread{
			DieChan: make(chan struct{},1),
			Run: run,
			Name: fmt.Sprintf("[RemoteLoginWorker-%d]",i),
		}

		worker.Run = run
		p.workers = append(p.workers,worker)
		worker.Init()
	}


	logger.Log.Infof("启动远程登录线程池，共启动协程数：%d",workerSize)
}

func (p *RemoteLoginThreadPool) Shutdown()  {
	for _,worker := range p.workers {
		worker.Shutdown()
	}
	close(p.loginChan)
}

func NewLoginThread() *LoginThread {
	p := &LoginThread{
		LogicThread:component.LogicThread{
			DieChan: make(chan struct{},1),
		},
		waitLoginQueue: queue.NewBaseQueue(typeOfLoginInfo),
		waitLoginQueueForGMs: queue.NewBaseQueue(typeOfLoginInfo),
		workerPool: &RemoteLoginThreadPool{
			loginChan:make(chan *model.LoginInfo),
			workers: []component.LogicThread{},
		},
		waitLoginMap: map[string]*list.Element{},
	}

	p.Run = func() {
		sleepTime := 100
		beginExecuteTime := util.Now()
		LoginManager.Tick(beginExecuteTime)
		if p.waitLoginQueue.Size() == 0 && p.waitLoginQueueForGMs.Size() == 0 {
			sleepTime = 1000
		}else {
			//可以登录到登录管道的人数
			canEnterRemoteLoginChanNum := p.getCanEnterLoginChanNum()
			if canEnterRemoteLoginChanNum > 0 {
				curEnterCount := 0
				for i:=0;i<canEnterRemoteLoginChanNum;i++ {
					if p.waitLoginQueueForGMs.Size() == 0 {
						break
					}

					curEnterCount ++
					p.workerPool.doRemoteLogin(p.waitLoginQueueForGMs.Poll().Value.(*model.LoginInfo))
				}

				for i:= curEnterCount;i<canEnterRemoteLoginChanNum;i++ {
					if p.waitLoginQueue.Size() == 0 {
						break
					}

					curEnterCount ++
					p.workerPool.doRemoteLogin(p.waitLoginQueue.Poll().Value.(*model.LoginInfo))
				}

				sleepTime = 100
			}else {
				sleepTime = 500
			}

			p.dealWithQueueIndex(p.waitLoginQueueForGMs)
			p.dealWithQueueIndex(p.waitLoginQueue)

			endExecuteTime := util.Now()
			executeTime := endExecuteTime - beginExecuteTime
			logger.Log.Debugf("线程%s，执行时长：%d，本轮可允许登录玩家数量：%d",p.GetName(),executeTime,canEnterRemoteLoginChanNum)

			time.Sleep(time.Duration(sleepTime) * time.Millisecond)
		}
	}

	return p
}

func (p *RemoteLoginThreadPool) doRemoteLogin(loginInfo *model.LoginInfo) bool {
	if loginInfo == nil {
		return false
	}

	p.loginChan <- loginInfo
	return true
}

/**
获取当前登录队列的登录人数
 */
func (p *RemoteLoginThreadPool) getCurrentLoginInfoNum() int {
	return len(p.loginChan)
}

/**
移除关闭session的登录 将最新的等待等待人数同步到客户端
 */
func (p *LoginThread) dealWithQueueIndex(waitLoginQueue *queue.BaseQueue) error {
	if waitLoginQueue.Size() == 0 {
		return nil
	}

	changeIndexMap := map[string]int{}
	index := 0
	var loginInfo *model.LoginInfo
	for e := waitLoginQueue.List.Front();e != nil ;e = e.Next() {
		v := e.Value
		if v == nil {
			continue
		}

		index ++
		loginInfo = v.(*model.LoginInfo)
		if loginInfo.QueueIndex != index {
			loginInfo.QueueIndex = index
			changeIndexMap[loginInfo.SessionId] = index
		}
	}

	if len(changeIndexMap) > 0 {
		removeList := make([]*list.Element,0)
		var sessionId string
		for sessionId,index = range changeIndexMap {
				e,ok := p.waitLoginMap[sessionId]
			if !ok {
				removeList = append(removeList,e)
				continue
			}

			//给客户端推送 当前等待登录人数
			num := int32(index)
			_,err := pitaya.SendPushToUsers(constants.LCLoginQueueIndexRetRoute,&protos2.LCLoginRet{Num: &num},[]string{sessionId}, constants.SvTypeConnector)
			if err != nil {
				logger.Log.Errorf("[LoginThread] dealWithQueueIndex failed,error=%s",err.Error())
				return err
			}
		}

		for _,e := range removeList {
			waitLoginQueue.Remove(e)
		}
	}

	return nil
}


/**
添加新的登录消息
 */
func (p *LoginThread) AddNewLoginMessage(info *model.LoginInfo) error {
	m := make(map[string]string,2)
	err := gojson.Unmarshal([]byte(info.ChannelJson),&m)
	if err != nil {
		logger.Log.Errorf("LoginThread AddNewLoginMessage failed,error=%s",err.Error())
		return err
	}

	username,ok := m["username"]

	if ok && GMAccountManager.IsGMAccount(username) {
		err = GMAccountManager.CheckInsertGMAccount(username,info.ChannelId,info.AppId)
		if err != nil {
			return err
		}
		p.waitLoginMap[info.SessionId] = p.waitLoginQueueForGMs.Offer(info)
	}else {
		p.waitLoginMap[info.SessionId] = p.waitLoginQueue.Offer(info)
	}

	return nil
}

/**
玩家可以断开连接 从等待登录中移除
 */
func (p *LoginThread) RemoveLoginMap(sessionIds ...string)  {
	for _,sessionId := range sessionIds {
		delete(p.waitLoginMap,sessionId)
	}

}

/**
获取可以进入登录队列的人数
 */
func (p *LoginThread) getCanEnterLoginChanNum() int {
	//正在登录玩家的人数
	currentLoginingPlayerNum := p.workerPool.getCurrentLoginInfoNum()
	//已经登录的玩家人数（登录服 + 游戏服上玩家总数）
	havingLoginingPlayerNum := LoginManager.getOnlinePlayerNum()
	//当前可以登录的玩家人数
	return pitaya.GetConfig().GetInt(constants2.LoginServerMaxOnlinePlayerNum) - (currentLoginingPlayerNum + havingLoginingPlayerNum)
}

/**
获取正在登录中的玩家人数
 */
func (p *LoginThread) GetDoLoginPlayerNum() int {
	return len(p.workerPool.loginChan)
}


